﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace comm.world
{
    public abstract class BSObject : BObject
    {
        #region BaseMoving
        abstract class BaseMoving
        {
            public abstract bool IsBegin { get; }
            public abstract int FromX { get; }
            public abstract int FromY { get; }
            public abstract int ToX { get; }
            public abstract int ToY { get; }
            public abstract long TimeOffset { get; }

            protected BSObject _sobj = null;

            public BaseMoving(BSObject sobj)
            {
                _sobj = sobj;
            }

            public abstract void End();
            public abstract void Begin(int x, int y, long useMS = 0);
            public abstract void Update(TimeStat timeStat);
        }
        #endregion

        #region Flight
        class Flight : BaseMoving
        {
            public override bool IsBegin { get { return _moving; } }
            public override int FromX { get { return _fromX; } }
            public override int FromY { get { return _fromY; } }
            public override int ToX { get { return _moveToX; } }
            public override int ToY { get { return _moveToY; } }
            public override long TimeOffset { get { return (int)_moveCountMS; } }

            int _fromX = 0, _fromY = 0;
            int _moveToX = 0, _moveToY = 0;
            double _startX = 0, _startY = 0;
            double _moveXStep = 0, _moveYStep = 0;
            double _moveCountMS = 0, _moveMaxMS = 0;
            long _useMS = 0;
            bool _moving = false;
            int[] _waitTo = null;
            long _waitUseMS = 0;

            public Flight(BSObject sobj)
                : base(sobj)
            {
            }

            void BeginMove(int x, int y, long useMS)
            {
                int locX = _sobj.LocX,
                    locY = _sobj.LocY;
                _fromX = locX;
                _fromY = locY;
                var len = comm.MathHelper.PointDistance(locX, locY, x, y);
                _moveMaxMS = (int)Math.Ceiling(len * _sobj.MoveSpeed);
                _moveCountMS = 0;
                _moveToX = x;
                _moveToY = y;
                _startX = locX;
                _startY = locY;
                _moveXStep = (_moveToX - locX) / (double)_moveMaxMS;
                _moveYStep = (_moveToY - locY) / (double)_moveMaxMS;
                if (useMS > 0)
                {
                    _useMS = useMS;
                }
                _sobj.OnBeginMove(locX, locY, _moveToX, _moveToY, _moveMaxMS);
                _moving = true;
            }

            public override void Begin(int x, int y, long useMS = 0)
            {
                if (_moving)
                {
                    _waitTo = new int[] { x, y };
                    _waitUseMS = useMS;
                }
                else
                {
                    BeginMove(x, y, useMS);
                }
            }
            public override void End()
            {
                _moving = false;
                _waitTo = null;
                _sobj.OnEndMove();
            }
            public override void Update(TimeStat timeStat)
            {
                var ms = timeStat.ElapsedTime.TotalMilliseconds;
                if (_moving)
                {
                    ms += _useMS;
                    _useMS = 0;

                    if (ms + _moveCountMS > _moveMaxMS)
                    {
                        ms = _moveMaxMS - _moveCountMS;
                    }
                    _startX += _moveXStep * ms;
                    _startY += _moveYStep * ms;

                    var locX = (int)Math.Ceiling(_startX);
                    var locY = (int)Math.Ceiling(_startY);

                    if (_sobj.LocX != locX || _sobj.LocY != locY)
                    {
                        _sobj.SetLoc(locX, locY);
                        if (_waitTo != null)
                        {
                            BeginMove(_waitTo[0], _waitTo[1], _waitUseMS);
                            _waitTo = null;
                            return;
                        }
                    }

                    _moveCountMS += ms;

                    _sobj.OnUpdateMove(ms);

                    if (_moveCountMS >= _moveMaxMS)
                    {
                        _sobj.SetLoc(_moveToX, _moveToY);
                        _moving = false;

                        if (_waitTo == null)
                        {
                            End();
                        }
                        else
                        {
                            BeginMove(_waitTo[0], _waitTo[1], _waitUseMS);
                            _waitTo = null;
                        }
                    }
                }
            }
        }
        #endregion

        #region Moving
        class Moving : BaseMoving
        {
            public override bool IsBegin { get { return _moving; } }
            public override int FromX { get { return _fromX; } }
            public override int FromY { get { return _fromY; } }
            public override int ToX { get { return _moveNextX; } }
            public override int ToY { get { return _moveNextY; } }
            public override long TimeOffset { get { return (int)_moveStepCountMS; } }
            public int[][] Steps { get { return _steps != null ? (int[][])_steps.Clone() : null; } }

            int[][] _steps = null;
            int _stepIndex = -1;

            int _fromX = 0, _fromY = 0;
            int _moveNextX = 0, _moveNextY = 0,
                _moveToX = 0, _moveToY = 0;
            double _moveStepCountMS = 0, _moveStepMaxMS = 0;
            bool _moving = false, _beginMove = false, _moveStep = false;
            int[][] _waitSteps = null;

            string _lastTimeoutId = "";
            string _lastFindPathId = "";

            public Moving(BSObject sobj)
                : base(sobj)
            {
            }

            void BeginMove(int x, int y, long useMS = 0)
            {
                var uMS = useMS;
                var begin = DateTime.Now;
                _sobj.BSpace.CancelFindPath(_lastFindPathId);
                _lastFindPathId = _sobj.BSpace.FindPath(_sobj.PathSize, _sobj.LocX, _sobj.LocY, x, y, (mode, ns) =>
                {
                    if (uMS > 0)
                    {
                        uMS += (int)(DateTime.Now - begin).TotalMilliseconds;
                    }

                    var steps = new int[ns.Length][];
                    for (int i = 0, len = ns.Length; i < len; i++)
                    {
                        steps[i] = new int[] { ns[i].X, ns[i].Y };
                    }
                    if (_moveStep)
                    {
                        _waitSteps = steps;
                    }
                    else
                    {
                        BeginMoveSteps(steps, uMS);
                    }
                });
            }

            public void BeginMoveSteps(int[][] steps, long useMS = 0)
            {
                if (steps.Length > 1)
                {
                    var first = steps[0];
                    var last = steps[steps.Length - 1];
                    _steps = steps;
                    _stepIndex = 0;
                    _fromX =
                    _moveToX = last[0];
                    _moveToY = last[1];
                    _moveStepCountMS = useMS > 0 ? useMS : 0;
                    _moveStepMaxMS = 500;// _sobj.MoveSpeed;
                    _sobj.OnBeginMove(first[0], first[1], last[0], last[1], _moveStepMaxMS * _steps.Length);
                    _moving = true;
                    _beginMove = true;
                }
            }

            public override void Begin(int x, int y, long useMS = 0)
            {
                _sobj.CancelTiming(_lastTimeoutId);
                BeginMove(x, y, useMS);
            }
            public override void End()
            {
                _moving = false;
                _moveStep = false;
                _waitSteps = null;
                _sobj.OnEndMove();
            }
            public override void Update(TimeStat timeStat)
            {
                var ms = timeStat.ElapsedTime.TotalMilliseconds;
                if (_moving)
                {
                    #region beginMove
                    if (_beginMove)
                    {
                        _beginMove = false;
                        if (_waitSteps != null)
                        {
                            if (_waitSteps.Length > 1)
                            {
                                BeginMoveSteps(_waitSteps);
                                _waitSteps = null;
                            }
                            else
                            {
                                End();
                            }
                        }
                        else
                        {
                            if (_stepIndex < _steps.Length - 1)
                            {
                                _stepIndex++;

                                int locX = _sobj.LocX, locY = _sobj.LocY;
                                int nextX = _steps[_stepIndex][0], nextY = _steps[_stepIndex][1];

                                if (_sobj.MoveType == objst.MoveType.GROUND || _sobj.LocMgr.TrySetPath(_sobj.ObjectId, _sobj.PathSize, locX, locY, nextX, nextY))
                                {
                                    _fromX = locX;
                                    _fromY = locY;
                                    _moveNextX = nextX;
                                    _moveNextY = nextY;

                                    _sobj.SetLoc(nextX, nextY);
                                    _sobj.OnBeginStepMove(locX, locY, nextX, nextY, _moveStepMaxMS);
                                    _moveStep = true;
                                }
                                else
                                {
                                    _sobj.CancelTiming(_lastTimeoutId);
                                    _lastTimeoutId = _sobj.Timeout(1000 * 4, count =>
                                    {
                                        BeginMove(_moveToX, _moveToY);
                                    });
                                }
                            }
                            else
                            {
                                End();
                            }
                        }
                    }
                    #endregion

                    #region moveStep
                    if (_moveStep)
                    {
                        if (_moveStepCountMS > _moveStepMaxMS)
                        {
                            ms = _moveStepMaxMS;
                        }
                        else
                        {
                            _moveStepCountMS += ms;
                        }

                        _sobj.OnUpdateMove(ms);

                        if (_moveStepCountMS >= _moveStepMaxMS)
                        {
                            _moveStepCountMS -= _moveStepMaxMS;
                            _moveStep = false;
                            _beginMove = true;
                        }
                    }
                    #endregion
                }
            }
        }
        #endregion

        public BSpace.LocManager LocMgr { get; private set; }
        public BSpace BSpace { get; private set; }
        public string ObjectId { get; private set; }
        public int LocX { get; private set; }
        public int LocY { get; private set; }
        public objst.MoveType MoveType { get; set; }
        public int MoveSpeed { get { return _moveSpeed; } set { _moveSpeed = value; } }
        public int PathSize { get { return _pathSize; } set { _pathSize = value; } }

        public bool IsObserver { get { return BSpace.LocMgr.IsObserver(ObjectId); } }
        public int ObservedRadius { get; set; }

        public bool Moveing
        {
            get
            {
                return _moving.IsBegin || _flight.IsBegin;
            }
        }
        public int[][] MovingSteps { get { return _moving.Steps; } }
        public int FromX { get { return MoveType == objst.MoveType.FLIGHT ? _flight.FromX : _moving.FromX; } }
        public int FromY { get { return MoveType == objst.MoveType.FLIGHT ? _flight.FromY : _moving.FromY; } }
        public int ToX { get { return MoveType == objst.MoveType.FLIGHT ? _flight.ToX : _moving.ToX; } }
        public int ToY { get { return MoveType == objst.MoveType.FLIGHT ? _flight.ToY : _moving.ToY; } }
        public long MoveTimeOffset { get { return MoveType == objst.MoveType.FLIGHT ? _flight.TimeOffset : _moving.TimeOffset; } }

        int _pathSize = 1, _moveSpeed = 100;
        Moving _moving = null;
        Flight _flight = null;

        public BSObject(BSpace space, BSpace.LocManager locManager, string objectId)
        {
            BSpace = space;
            ObjectId = objectId;
            LocMgr = locManager;

            _moving = new Moving(this);
            _flight = new Flight(this);
        }

        protected virtual void OnBeginMove(int fromX, int fromY, int toX, int toY, double maxMS)
        {
        }
        protected virtual void OnBeginStepMove(int formX, int fromY, int toX, int toY, double maxMS)
        {
        }
        protected virtual void OnEndMove()
        {
        }
        protected virtual void OnUpdateMove(double countMS)
        {
        }
        protected virtual void OnSetLoc(int oldX, int oldY, int newX, int newY)
        {
        }

        public virtual void ReceiveResultOfObserved(ObservedResultType type, ObservedResult result)
        {
        }
        public ObservedResult[] GetAllResultOfObserved()
        {
            var arr = new ObservedResult[] { };
            var observer = LocMgr.GetObserver(ObjectId);
            if (observer != null)
            {
                arr = observer.GetAllResult();
            }
            return arr;
        }
        public void StartObserved()
        {
            LocMgr.AddObserver(this);
        }
        public void StopObserved()
        {
            LocMgr.RemoveObserver(ObjectId);
        }

        public bool SetLoc(int x, int y)
        {
            int fromX = LocX, fromY = LocY;
            if (MoveType == objst.MoveType.FLIGHT || MoveType == objst.MoveType.GROUND || Moveing)
            {
                LocX = x;
                LocY = y;
                LocMgr.Set(ObjectId, PathSize, fromX, fromY, x, y);
                OnSetLoc(fromX, fromY, LocX, LocY);
            }
            else
            {
                if (LocMgr.TrySetPath(ObjectId, PathSize, LocX, LocY, x, y))
                {
                    LocX = x;
                    LocY = y;
                    LocMgr.Set(ObjectId, PathSize, fromX, fromY, x, y);
                    OnSetLoc(fromX, fromY, LocX, LocY);
                }
                else
                {
                    return false;
                }
            }
            return true;
        }
        public void BeginMove(int x, int y, long useMS = 0)
        {
            if (MoveType == objst.MoveType.FLIGHT)
            {
                _flight.Begin(x, y, useMS);
            }
            else
            {
                _moving.Begin(x, y, useMS);
            }
        }
        public void BeginMoveSteps(int[][] steps, long useMS = 0)
        {
            if (MoveType != objst.MoveType.FLIGHT)
            {
                _moving.BeginMoveSteps(steps, useMS);
            }
        }
        public void EndMove()
        {
            if (MoveType == objst.MoveType.FLIGHT)
            {
                _flight.End();
            }
            else
            {
                _moving.End();
            }
        }

        protected override void OnUpdate(TimeStat timeStat)
        {
            _moving.Update(timeStat);
            _flight.Update(timeStat);
        }
    }
}
